_ = require 'lodash'

util = require '../util'
log = require '../log'
error = require '../error'
Meta = require '../meta/Meta'

mongoService = require './EntityServiceMongo'
mysqlService = require './EntityServiceMysql'
EntityServiceCache = require './EntityServiceCache'
Interceptor = require './EntityServiceInterceptor'
getInterceptor = Interceptor.getInterceptor

# 插入
exports.gCreate = (ctx, entityName, instance)->
    throw new error.SystemError("CreateEmpty") unless _.size(instance)

    entityMeta = Meta.getEntityMeta entityName

    instance._version = 1
    instance._createdOn = new Date() # TODO _createdOn
    instance._modifiedOn = instance._createdOn

    gIntercept = getInterceptor entityName, Interceptor.Actions.Create
    try
        yield from gIntercept ctx, instance, ->
            id = if entityMeta.db == Meta.DB.mysql
                yield from mysqlService.gCreate ctx, entityMeta, instance
            else
                yield from mongoService.gCreate entityMeta, instance

            instance._id = id
            return {_id: id}
    finally
        yield from EntityServiceCache.gFireEntityCreated(ctx, entityName) # 很可能实体还是被某种程度修改，导致缓存失效

# 批量更新
# 每一对都要校验 Id/Version
exports.gUpdateMany = (ctx, entityName, idVersions, patch)->
    delete patch._version
    delete patch._id
    return unless _.size(patch)

    patch._modifiedOn = new Date()

    entityMeta = Meta.getEntityMeta entityName

    gIntercept = getInterceptor entityName, Interceptor.Actions.Update
    try
        yield from gIntercept ctx, idVersions, patch, ->
            if entityMeta.db == Meta.DB.mysql
                yield from mysqlService.gUpdateMany ctx, entityMeta, idVersions, patch
            else
                yield from mongoService.gUpdateMany entityMeta, idVersions, patch
            true
    finally
        ids = (p._id for p in idVersions)
        yield from EntityServiceCache.gFireEntityUpdated(ctx, entityName, ids)

# MongoDb
exports.gNativeUpdateMongoById = (ctx, entityName, _id, change)->
    $set = change.$set
    if $set
        delete $set._version
        delete $set._id
    else
        change.$set = {}

    change.$set._modifiedOn = new Date()

    change.$inc = change.$inc || {}
    change.$inc._version = 1

    try
        entityMeta = Meta.getEntityMeta entityName
        yield from mongoService.gNativeUpdate entityMeta, {_id: _id}, change
        true
    finally
        yield from EntityServiceCache.gFireEntityUpdated(ctx, entityName, [_id])

# 批量删除
exports.gRemoveMany = (ctx, entityName, ids)->
    return unless ids?.length
    # 不使用 gRemoveManyByCriteria 的实现；因为它要加载实体才能获得 ids；而这里 ids 已经知道了
    entityMeta = Meta.getEntityMeta entityName

    gIntercept = getInterceptor entityName, Interceptor.Actions.Remove
    try
        yield from gIntercept ctx, ids, ->
            if entityMeta.db == Meta.DB.mysql
                yield from mysqlService.gRemoveMany ctx, entityMeta, ids
            else
                yield from mongoService.gRemoveMany entityMeta, ids
            true
    finally
        yield from EntityServiceCache.gFireEntityRemoved(ctx, entityName, ids)

# 批量删除
exports.gRemoveManyByCriteria = (ctx, entityName, criteria)->
    entityMeta = Meta.getEntityMeta entityName

    entities = yield from exports.gList(ctx, {entityName, criteria, includedFields: ["_id"], withoutTotal: true})
    # log.debug 'es', entities
    ids = (e._id for e in entities)
    unless ids.length
        log.debug 'remove by criteria, size=0'
        return

    gIntercept = getInterceptor entityName, Interceptor.Actions.Remove
    try
        yield from gIntercept ctx, ids, ->
            if entityMeta.db == Meta.DB.mysql
                yield from mysqlService.gRemoveMany ctx, entityMeta, ids
            else
                yield from mongoService.gRemoveMany entityMeta, ids
            true
    finally
        yield from EntityServiceCache.gFireEntityRemoved(ctx, entityName, ids)

# 批量找回
exports.gRecoverMany = (ctx, entityName, ids)->
    entityMeta = Meta.getEntityMeta entityName

    gIntercept = getInterceptor entityName, Interceptor.Actions.Recover
    try
        yield from gIntercept ctx, ids, ->
            if entityMeta.db == Meta.DB.mysql
                yield from mysqlService.gRecoverMany ctx, entityMeta, ids
            else
                yield from mongoService.gRecoverMany entityMeta, ids
            true
    finally
        yield from EntityServiceCache.gFireEntityCreated(ctx, entityName)

exports.gFindOneById = (ctx, repo, entityName, id, includedFields)->
    entityMeta = Meta.getEntityMeta entityName

    cacheId = id + "|" + repo + "|" + includedFields?.join(",")
    criteria = {field: '_id', operator: '==', value: id}
    yield from EntityServiceCache.gWithByIdCache entityName, cacheId, ->
        if entityMeta.db == Meta.DB.mysql
            yield from mysqlService.gFindOneByCriteria ctx, repo, entityMeta, criteria, includedFields
        else
            yield from mongoService.gFindOneByCriteria repo, entityMeta, criteria, includedFields

exports.gFindOneByCriteria = (ctx, repo, entityName, criteria, includedFields)->
    entityMeta = Meta.getEntityMeta entityName

    cacheId = "OneByCriteria|" + repo + "|" + JSON.stringify(criteria) + "|" + includedFields?.join(",")

    criteria = tryConvertObjectCriteria criteria
    yield from EntityServiceCache.gWithOtherCache entityName, cacheId, ->
        if entityMeta.db == Meta.DB.mysql
            yield from mysqlService.gFindOneByCriteria ctx, repo, entityMeta, criteria, includedFields
        else
            yield from mongoService.gFindOneByCriteria repo, entityMeta, criteria, includedFields

exports.gFindManyById = (ctx, repo, entityName, ids, includedFields)->
    entityMeta = Meta.getEntityMeta entityName

    cacheId = "ManyById|" + repo + "|" + _.join(ids, ";") + "|" + includedFields?.join(",")
    criteria = {field: '_id', operator: 'in', value: ids}
    yield from EntityServiceCache.gWithOtherCache entityName, cacheId, ->
        if entityMeta.db == Meta.DB.mysql
            yield from mysqlService.gList ctx, {repo, entityName, criteria, includedFields, withoutTotal: true}
        else
            yield from mongoService.gList ctx, {repo, entityName, criteria, includedFields, withoutTotal: true}

exports.gList = (ctx, {entityName, repo, criteria, pageNo, pageSize, sortCriteria, includedFields, withoutTotal})->
    entityMeta = Meta.getEntityMeta entityName

    pageNo = 1 unless pageNo >= 1
    sortCriteria = sortCriteria || {}

    criteriaString = JSON.stringify(criteria)
    sortCriteriaString = util.objectToKeyValuePairString(sortCriteria)
    includedFieldsString = includedFields?.join(',')

    cacheId = "List|#{repo}|#{pageNo}|#{pageSize}|#{criteriaString}|#{sortCriteriaString}|#{includedFieldsString}"

    criteria = tryConvertObjectCriteria criteria
    yield from EntityServiceCache.gWithOtherCache entityName, cacheId, ->
        cr = {repo, entityMeta, criteria, includedFields, sortCriteria, pageNo, pageSize, withoutTotal}
        if entityMeta.db == Meta.DB.mysql
            yield from mysqlService.gList ctx, cr
        else
            yield from mongoService.gList cr

tryConvertObjectCriteria = (object)->
    return object unless object?
    if object.relation? and object.items? or object.field? and object.operator?
        return object # 是标准 Criteria
    else
        items = ({field: k, value: v, operator: '=='} for k, v of object)
        if items.length == 1
            items[0]
        else
            {relation: 'and', items}
